#!/usr/bin/python2.4


import cgi
import cStringIO
import itertools
import operator
import os
import re
import traceback
import urllib

import constants
from dioxide import Dioxide
import html_formatter
import http_server
from tag import tag
import utils


class Dashboard(object):

  def __init__(self, oracle, notify_change_callback=None):
    self.oracle = oracle
    self.notify_change_callback = notify_change_callback
    self.file_cache = {}

  def setup(self):
    register_path = http_server.RequestHandler.register_path
    register_path('/resources/',
                  os.path.join(os.path.dirname(__file__), 'resources'))
    register_handler = http_server.RequestHandler.register_handler
    for name in dir(self):
      if not name.startswith('_') and name.endswith('_handler'):
        attr = getattr(self, name)
        if callable(attr):
          if name == 'root_handler':
            path = '/$'
          else:
            path = '/%s/?$' % name[:-8].replace('_', '/')
          register_handler(path, attr)

  def notify_change(self):
    if self.notify_change_callback:
      return self.notify_change_callback()

  def start(self, port):
    return http_server.start(port)

  def render_reply(self, handler, title, contents):
    html = html_formatter.create_html(handler, title, contents)
    s = cStringIO.StringIO()
    html.render_html(s.write)
    return 200, s

  def get_root_page_links(self):
    return [tag.ul(tag.li(tag.a('Hosts', href='/hosts')),
                   tag.li(tag.a('Results', href='/results')),
                   tag.li(tag.a('Host Results', href='/results/hosts')),
                   tag.li(tag.a('Runtime', href='/runtime')),
                   tag.li(tag.a('Host Runtime', href='/runtime/hosts')),
                   tag.li(tag.a('Status', href='/status')),
                   tag.li(tag.a('Inspector', href='/inspector')))]

  def root_handler(self, handler, parsed_url, query_dict):
    test_plan = self.oracle.test_plan
    test_results = self.oracle.test_results
    try:
      test_results.lock.acquire()
      if test_results.overall_stats:
        overall_duration = test_results.overall_stats.total_count
        hosts = test_results.stats_hosts
      else:
        overall_duration = None
        hosts = test_plan.hosts
    finally:
      test_results.lock.release()
    contents = [
      html_formatter.create_test_plan_list_table(test_plan, hosts,
                                                 overall_duration),
      tag.h2('Tests:', _s=True),
      html_formatter.create_tests_table(test_plan.tests),
      tag.h2('Hosts:', _s=True),
      html_formatter.create_hosts_list_div(hosts),
      tag.h2('Links:', _s=True)]
    contents.extend(self.get_root_page_links())

    return self.render_reply(handler, test_plan.name, contents)

  def test_handler(self, handler, parsed_url, query_dict):
    test_plan = self.oracle.test_plan
    job_name = urllib.unquote(parsed_url[4])
    try:
      test = test_plan.get_test(job_name)
    except KeyError:
      return self.render_reply(
          handler, '%s: Test' % test_plan.name,
          [tag.p('Test not found: %s' % cgi.escape(utils.nou_repr(job_name)))])
    title = '%s: Test - %s' % (test_plan.name, cgi.escape(job_name))
    contents = [html_formatter.create_test_list_table(test)]
    return self.render_reply(handler, title, contents)

  def get_hosts_page_groups(self, hosts):
    return [tag.h2('By State:', _s=True),
            html_formatter.create_hosts_group_by_state_ul(hosts),
            tag.h2('By Notes:', _s=True),
            html_formatter.create_hosts_group_by_notes_ul(hosts),
            tag.h2('By Labels:', _s=True),
            html_formatter.create_hosts_group_by_labels_ul(hosts)]

  def hosts_handler(self, handler, parsed_url, query_dict):

    def parse_query():
      try:
        hostname = '|'.join(itertools.ifilter(
            None, itertools.imap(str.strip, query_dict['hostname'])))
      except KeyError:
        hostname = ''
      try:
        offset = int(query_dict['offset'][-1])
      except (KeyError, IndexError, ValueError):
        offset = 0
      try:
        limit = int(query_dict['limit'][-1])
      except (KeyError, IndexError, ValueError):
        limit = 20
      return hostname, offset, limit

    test_plan = self.oracle.test_plan
    hostname, offset, limit = parse_query()

    hostname_re = re.compile(hostname, re.UNICODE)
    hosts = [host
             for host in test_plan.hosts
             if hostname_re.match(host.hostname)]

    title = '%s: Hosts' % test_plan.name
    if hostname:
      title += ' - %s' % cgi.escape(hostname)
    form = tag.form(tag.b('Hostname:', _s=True),
                    tag.input(name='hostname', type='text',
                              value=cgi.escape(hostname, True),
                              _nc=True),
                    tag.input(type='submit', value='Go', _nc=True))
    base_url = '?'
    if hostname:
      base_url += '%s&' % urllib.urlencode((('hostname', hostname),))
    contents = [form] + self.get_hosts_page_groups(hosts) + [
        tag.a(tag.h2('Status:'), name='host_status', _s=True),
        html_formatter.create_table_with_page_links_table(
            hosts, offset, limit, base_url,
            html_formatter.create_hosts_table,
            '#host_status')
    ]
    return self.render_reply(handler, title, contents)

  def hosts_notes_handler(self, handler, parsed_url, query_dict):

    def parse_query():
      try:
        hostname = ' '.join(query_dict['hostname']).strip()
      except KeyError:
        hostname = ''
      try:
        set_notes = query_dict['set_notes'][-1].strip()
      except (KeyError, IndexError):
        set_notes = None
      try:
        notes = query_dict['notes'][-1].strip()
      except (KeyError, IndexError):
        notes = None
      return hostname, set_notes, notes

    def get_hosts(hostname):
      all_hosts = test_plan.hosts
      hostnames = hostname.replace(',', ' ').split()
      hostnames.sort(key=utils.hostname_sort_key)
      hosts = []
      lo = 0
      hi = len(all_hosts)
      for hostname in hostnames:
        hostname_sort_key = utils.hostname_sort_key(hostname)
        lo = utils.bisect_left(all_hosts, hostname_sort_key,
                               lo, hi, operator.attrgetter('sort_key'))
        if lo == hi or all_hosts[lo].sort_key != hostname_sort_key:
          return hostname
        hosts.append(all_hosts[lo])
        lo += 1
      return hosts

    def validate_query(hostname, set_notes, notes):
      if hostname and set_notes == 'true' and not handler.localhost:
        return False, [tag.p('Unauthorized: hostname %s, notes %s' %
                             (cgi.escape(utils.nou_repr(hostname)),
                              cgi.escape(utils.nou_repr(notes))))]
      hosts = get_hosts(hostname)
      if isinstance(hosts, basestring):
        return False, [tag.p('Host not found: %s' %
                             cgi.escape(utils.nou_repr(hosts)))]
      return True, hosts

    def process_submission(hosts, notes):
      if notes == '':
        notes = None
      Dioxide.set_notes(hosts, notes, update_objects=True)
      return [tag.p('Notes for', tag.b(hostname), 'submitted:',
                    cgi.escape(utils.nou_repr(notes)),
                    _s=True, _sep=' ')]

    def render_input_page(hosts, notes):
      if len(hosts) == 1:
        notes = hosts[0].notes
      elif len(hosts) > 1:
        notes = ''
      if notes is None:
        notes = ''
      table = tag.table
      hostname_input = tag.input(name='hostname', type='text', size=35,
                                 value=cgi.escape(hostname, True),
                                 id='hostname_text', _nc=True)
      table += tag.tr(
          tag.th('Hostname:', _s=True),
          tag.td(hostname_input, _s=True))
      notes_inputs = [tag.input(name='set_notes', type='radio', value='false',
                                checked='checked', id='set_notes_false',
                                _nc=True),
                      tag.label('No Change', for_='set_notes_false', _s=True),
                      tag.br(_nc=True),
                      tag.input(name='set_notes', type='radio', value='true',
                                _nc=True),
                      tag.input(name='notes', type='text', size=32,
                                value=cgi.escape(notes, True),
                                _nc=True)]
      if not handler.localhost:
        for e in notes_inputs:
          if not isinstance(e, basestring) and e.name == 'input':
            e['disabled'] = 'disabled'
      table += tag.tr(
          tag.th('Notes:', _s=True),
          tag.td(*notes_inputs))
      table += tag.tr(
          tag.td(tag.input(type='reset', value='Reset', _nc=True), _s=True),
          tag.td(tag.input(type='submit', value='Update', _nc=True),
                 align='right', _s=True))
      return [tag.form(table)]

    test_plan = self.oracle.test_plan
    hostname, set_notes, notes = parse_query()
    valid, contents = validate_query(hostname, set_notes, notes)
    title = '%s: Host Notes' % test_plan.name
    if valid:
      hosts = contents
      if len(hosts) == 1:
        title += ' - %s' % cgi.escape(hosts[0].hostname)
      elif len(hosts) > 1:
        title += ' - %s hosts' % len(hosts)
      if hosts and set_notes == 'true':
        contents = process_submission(hosts, notes)
      else:
        contents = render_input_page(hosts, notes)

    return self.render_reply(handler, title, contents)

  def results_handler(self, handler, parsed_url, query_dict):
    test_plan = self.oracle.test_plan
    test_results = self.oracle.test_results
    try:
      test_results.lock.acquire()
      overall_stats = test_results.overall_stats
      test_stats = test_results.test_stats
    finally:
      test_results.lock.release()
    title = '%s: Results' % test_plan.name
    contents = [
        tag.h2('Overall Progress:', _s=True),
        html_formatter.create_overall_results_table(
            overall_stats, test_results.get_sql_query_conditions),
        tag.h2('Test Progress:', _s=True),
        html_formatter.create_tests_results_table(
            test_stats, test_plan.tests,
            test_results.get_sql_query_conditions)
    ]
    return self.render_reply(handler, title, contents)

  def results_hosts_handler(self, handler, parsed_url, query_dict):

    def parse_query():
      try:
        hostname = '|'.join(itertools.ifilter(
            None, itertools.imap(str.strip, query_dict['hostname'])))
      except KeyError:
        hostname = ''
      try:
        offset = int(query_dict['offset'][-1])
      except (KeyError, IndexError, ValueError):
        offset = 0
      try:
        limit = int(query_dict['limit'][-1])
      except (KeyError, IndexError, ValueError):
        limit = 20
      return hostname, offset, limit

    test_plan = self.oracle.test_plan
    test_results = self.oracle.test_results

    hostname, offset, limit = parse_query()
    hostname_re = re.compile(hostname, re.UNICODE)

    try:
      test_results.lock.acquire()
      hosts = [host
               for host in test_results.stats_hosts
               if hostname_re.match(host.hostname)]
      host_stats = test_results.host_stats
    finally:
      test_results.lock.release()

    title = '%s: Host Results' % test_plan.name
    if hostname:
      title += ' - %s' % cgi.escape(hostname)
    form = tag.form(tag.b('Hostname:', _s=True),
                    tag.input(name='hostname', type='text',
                              value=cgi.escape(hostname, True),
                              _nc=True),
                    tag.input(type='submit', value='Go', _nc=True))
    base_url = '?'
    if hostname:
      base_url += '%s&' % urllib.urlencode((('hostname', hostname),))
    contents = [
        form,
        tag.h2('Host Progress:', _s=True),
        html_formatter.create_table_with_page_links_table(
            hosts, offset, limit, base_url,
            lambda hosts: html_formatter.create_hosts_results_table(
                host_stats, hosts, test_results.get_sql_query_conditions))
    ]
    return self.render_reply(handler, title, contents)

  def runtime_handler(self, handler, parsed_url, query_dict):
    test_plan = self.oracle.test_plan
    test_results = self.oracle.test_results
    try:
      test_results.lock.acquire()
      overall_run_stats = test_results.overall_run_stats
      test_run_stats = test_results.test_run_stats
    finally:
      test_results.lock.release()
    title = '%s: Runtime' % test_plan.name
    contents = [
        tag.h2('Overall Runtime:', _s=True),
        html_formatter.create_overall_runtime_table(
            overall_run_stats, test_results.get_sql_query_conditions),
        tag.h2('Test Runtime:', _s=True),
        html_formatter.create_tests_runtime_table(
            test_run_stats, test_plan.tests,
            test_results.get_sql_query_conditions)
    ]
    return self.render_reply(handler, title, contents)

  def runtime_hosts_handler(self, handler, parsed_url, query_dict):

    def parse_query():
      try:
        hostname = '|'.join(itertools.ifilter(
            None, itertools.imap(str.strip, query_dict['hostname'])))
      except KeyError:
        hostname = ''
      try:
        offset = int(query_dict['offset'][-1])
      except (KeyError, IndexError, ValueError):
        offset = 0
      try:
        limit = int(query_dict['limit'][-1])
      except (KeyError, IndexError, ValueError):
        limit = 20
      return hostname, offset, limit

    test_plan = self.oracle.test_plan
    test_results = self.oracle.test_results

    hostname, offset, limit = parse_query()
    hostname_re = re.compile(hostname, re.UNICODE)

    try:
      test_results.lock.acquire()
      hosts = [host
               for host in test_results.run_stats_hosts
               if hostname_re.match(host.hostname)]
      host_run_stats = test_results.host_run_stats
    finally:
      test_results.lock.release()

    title = '%s: Host Runtime' % test_plan.name
    if hostname:
      title += ' - %s' % cgi.escape(hostname)
    form = tag.form(tag.b('Hostname:', _s=True),
                    tag.input(name='hostname', type='text',
                              value=cgi.escape(hostname, True),
                              _nc=True),
                    tag.input(type='submit', value='Go', _nc=True))
    base_url = '?'
    if hostname:
      base_url += '%s&' % urllib.urlencode((('hostname', hostname),))
    contents = [
        form,
        tag.h2('Host Runtime:', _s=True),
        html_formatter.create_table_with_page_links_table(
            hosts, offset, limit, base_url,
            lambda hosts: html_formatter.create_hosts_runtime_table(
                host_run_stats, hosts, test_results.get_sql_query_conditions))
    ]
    return self.render_reply(handler, title, contents)

  def status_handler(self, handler, parsed_url, query_dict):

    def parse_query():
      try:
        hostname = '|'.join(itertools.ifilter(
            None, itertools.imap(str.strip, query_dict['hostname'])))
      except KeyError:
        hostname = ''
      try:
        job_name = '|'.join(itertools.ifilter(
            None, itertools.imap(str.strip, query_dict['test'])))
      except KeyError:
        job_name = ''
      try:
        offset = int(query_dict['offset'][-1])
      except (KeyError, IndexError, ValueError):
        offset = 0
      try:
        limit = int(query_dict['limit'][-1])
      except (KeyError, IndexError, ValueError):
        limit = 20
      return hostname, job_name, offset, limit

    test_plan = self.oracle.test_plan
    test_results = self.oracle.test_results

    hostname, job_name, offset, limit = parse_query()
    hostname_re = re.compile(hostname, re.UNICODE)
    job_name_re = re.compile(job_name, re.UNICODE)

    tests = [test for test in test_plan.tests
             if job_name_re.match(test.job_name)]
    try:
      test_results.lock.acquire()
      hosts = [host
               for host in test_results.hosts
               if hostname_re.match(host.hostname)]
      host_job_status= test_results.host_job_status
    finally:
      test_results.lock.release()

    title = '%s: Status' % test_plan.name
    if hostname or job_name:
      title += ' - '
      if hostname:
        title += cgi.escape(hostname)
      if job_name:
        title += ' with %s' % cgi.escape(job_name)
    form = tag.form(tag.b('Hostname:', _s=True),
                    tag.input(name='hostname', type='text',
                              value=cgi.escape(hostname, True),
                              _nc=True),
                    tag.b('Test:', _s=True),
                    tag.input(name='test', type='text',
                              value=cgi.escape(job_name, True),
                              _nc=True),
                    tag.input(type='submit', value='Go', _nc=True))
    base_url = '?'
    if hostname:
      base_url += '%s&' % urllib.urlencode((('hostname', hostname),))
    if job_name:
      base_url += '%s&' % urllib.urlencode((('test', job_name),))
    contents = [
        form,
        tag.h2('Status:', _s=True),
        html_formatter.create_table_with_page_links_table(
            hosts, offset, limit, base_url,
            lambda hosts: html_formatter.create_status_table(
                hosts, tests, host_job_status))
    ]
    return self.render_reply(handler, title, contents)

  def inspector_handler(self, handler, parsed_url, query_dict):
    test_plan = self.oracle.test_plan
    test_results = self.oracle.test_results
    title = '%s: Inspector' % test_plan.name

    if test_results.inspector_path:
      inspector_path = test_results.inspector_path
      try:
        source = file(test_results.inspector_path).read()
      except:
        source = traceback.format_exc()
    else:
      inspector_path = ''
      source = ''

    if test_results.inspector:
      inspector_vars = [(k, v)
                        for k, v in vars(test_results.inspector).iteritems()
                        if not k.startswith('_')]
    else:
      inspector_vars = []

    contents = [
        html_formatter.create_list_table_div(
            (('Path', inspector_path),
             ('Source', '\n%s' % source))),
        tag.h2('Vars:'),
        html_formatter.create_list_table_div(inspector_vars)
    ]
    return self.render_reply(handler, title, contents)

  def triage_handler(self, handler, parsed_url, query_dict):

    def parse_query():
      try:
        job = ' '.join(query_dict['job']).strip()
      except KeyError:
        job = ''
      try:
        set_triage_info = query_dict['set_triage_info'][-1].strip()
      except (KeyError, IndexError):
        set_triage_info = None
      try:
        triage_info = query_dict['triage_info'][-1].strip()
      except (KeyError, IndexError):
        triage_info = None
      try:
        label = query_dict['label'][-1].strip()
      except (KeyError, IndexError):
        label = ''
      return job, set_triage_info, triage_info, label

    def get_job_host_runs(job):
      try:
        test_results.lock.acquire()
        job_tag_to_run = test_results.job_tag_to_run
        all_job_host_runs = test_results.all_job_host_runs
      finally:
        test_results.lock.release()

      jobs = job.replace(',', ' ').split()
      job_host_runs = []
      job_idxes = []
      for job in jobs:
        if job.isdigit():
          job_idxes.append(int(job))
        else:
          job_host_run = job_tag_to_run.get(job)
          if job_host_run:
            job_host_runs.append(job_host_run)
          else:
            return job

      job_idxes.sort()
      lo = 0
      hi = len(all_job_host_runs)
      for job_idx in job_idxes:
        lo = utils.bisect_left(all_job_host_runs, job_idx, lo, hi,
                               key=operator.attrgetter('job_idx'))
        if lo == hi or all_job_host_runs[lo].job_idx != job_idx:
          return 'job_idx: %s' % job_idx
        job_host_runs.append(all_job_host_runs[lo])
        lo += 1

      job_host_runs.sort(key=operator.attrgetter('job_idx'))
      return job_host_runs

    def validate_query(job, set_triage_info, triage_info, label):
      if (job and (set_triage_info == 'true' or label) and
          not handler.localhost):
        return False, [
            tag.p('Unauthorized: job %s, triage_info %s label %s' %
                  (cgi.escape(utils.nou_repr(job)),
                   cgi.escape(utils.nou_repr(triage_info)),
                   cgi.escape(utils.nou_repr(label))))]
      job_host_runs = get_job_host_runs(job)
      if isinstance(job_host_runs, basestring):
        return False, [tag.p('Job not found: %s' %
                             cgi.escape(utils.nou_repr(job_host_runs)))]

      if label and label != 'Clear' and label not in constants.ALL_TL_LABELS:
        return False, [tag.p('Invalid label: %s' %
                             cgi.escape(utils.nou_repr(label)))]

      all_failed = None
      if job_host_runs and (set_triage_info == 'true' or label):
        all_failed = True
        for job_host_run in job_host_runs:
          if not job_host_run.test_views:
            return False, [
                tag.p('Job with no test views: %s' %
                      cgi.escape(utils.nou_repr(job_host_run.job_tag)))]
          elif not job_host_run.first_failed_test_view:
            all_failed = False

      return True, (job_host_runs, all_failed)

    def process_submission(job_host_runs, triage_info, label):
      changed = False
      contents = []
      if set_triage_info == 'true':
        if triage_info == '':
          triage_info = None
        Dioxide.set_triage_info(job_host_runs, triage_info,
                                update_objects=True)
        changed = True
        contents.append(tag.p('Triage info for', tag.b(job), 'submitted:',
                              cgi.escape(utils.nou_repr(triage_info)),
                              _s=True, _sep=' '))
      if label:
        if label == 'Clear':
          label = None
        must_fail = label in (constants.TL_RESOLVED, constants.TL_NO_RERUN)
        if must_fail and not all_failed:
          contents.append(tag.p('Unable to add label for', tag.b(job, ':'),
                                cgi.escape(utils.nou_repr(label)),
                                _s=True, _sep=' '))
        else:
          Dioxide.set_label(job_host_runs, label, must_fail=must_fail,
                            update_objects=True)
          changed = True
          contents.append(tag.p('Label for', tag.b(job), 'added:',
                                cgi.escape(utils.nou_repr(label)),
                                _s=True, _sep=' '))
      if changed:
        self.notify_change()
      return contents

    def render_input_page(job_host_runs, triage_info, label):
      if len(job_host_runs) == 1:
        triage_info = job_host_runs[0].triage_info
      elif len(job_host_runs) > 1:
        triage_info = ''
      if triage_info is None:
        triage_info = ''
      table = tag.table
      job_input = tag.input(name='job', type='text', size=64,
                            value=cgi.escape(job, True),
                            id='job_text', _nc=True)
      table += tag.tr(
          tag.th('Job:', _s=True),
          tag.td(job_input, _s=True))
      triage_info_inputs = [tag.input(name='set_triage_info', type='radio',
                                      value='false', checked='checked',
                                      id='set_triage_info_false', _nc=True),
                            tag.label('No Change',
                                      for_='set_triage_info_false', _s=True),
                            tag.br(_nc=True),
                            tag.input(name='set_triage_info', type='radio',
                                      value='true', _nc=True),
                            tag.input(name='triage_info', type='text', size=61,
                                      value=cgi.escape(triage_info, True),
                                      _nc=True)]
      if not handler.localhost:
        for e in triage_info_inputs:
          if not isinstance(e, basestring) and e.name == 'input':
            e['disabled'] = 'disabled'
      table += tag.tr(
          tag.th('Triage Info:', _s=True),
          tag.td(*triage_info_inputs))
      label_inputs = [tag.input(name='label', type='radio', value='',
                                checked='checked', id='label_no_change',
                                _nc=True),
                      tag.label('No Change', for_='label_no_change', _s=True),
                      tag.br(_nc=True),
                      tag.input(name='label', type='radio', value='Clear',
                                id='label_clear', _nc=True),
                      tag.label('Clear', for_='label_clear', _s=True)]
      for l in constants.ALL_TL_LABELS:
        label_id = 'label_%s' % l.replace(' ', '_').lower()
        label_inputs.append(tag.input(name='label', type='radio', value=l,
                                      id=label_id, _nc=True))
        label_inputs.append(tag.label("Add '%s'" % l, for_=label_id, _s=True))
      if not handler.localhost:
        for e in label_inputs:
          if not isinstance(e, basestring) and e.name == 'input':
            e['disabled'] = 'disabled'
      table += tag.tr(
          tag.th('Label:', _s=True),
          tag.td(*label_inputs))
      table += tag.tr(
          tag.td(tag.input(type='reset', value='Reset', _nc=True), _s=True),
          tag.td(tag.input(type='submit', value='Update', _nc=True),
                 align='right', _s=True))
      contents = [tag.form(table)]
      if len(job_host_runs) == 1:
        contents += [
            tag.h2('Job:', _s=True),
            html_formatter.create_job_host_run_list_table(
                job_host_runs[0],
                test_plan.job_name_to_test[
                    job_host_runs[0].test_job_name].duration),
            tag.h2('Test Views:', _s=True),
            html_formatter.create_test_views_table(job_host_runs[0])]
      elif len(job_host_runs) > 1:
        contents += [
            tag.h2('Runs:', _s=True),
            html_formatter.create_job_host_runs_table(
                job_host_runs, test_plan.job_name_to_test)]
      return contents

    test_plan = self.oracle.test_plan
    test_results = self.oracle.test_results
    job, set_triage_info, triage_info, label = parse_query()
    valid, contents = validate_query(job, set_triage_info, triage_info, label)
    title = '%s: Triage' % test_plan.name
    if valid:
      job_host_runs, all_failed = contents
      if len(job_host_runs) == 1:
        title += ' - %s' % cgi.escape(job_host_runs[0].job_tag)
      elif len(job_host_runs) > 1:
        title += ' - %s runs' % len(job_host_runs)
      if job_host_runs and (set_triage_info == 'true' or label):
        contents = process_submission(job_host_runs, triage_info, label)
      else:
        contents = render_input_page(job_host_runs, triage_info, label)

    return self.render_reply(handler, title, contents)

  def reload_handler(self, handler, parsed_url, query_dict):
    test_plan = self.oracle.test_plan
    title = '%s: Reload' % test_plan.name
    if not handler.localhost:
      return self.render_reply(handler, title, [tag.p('Unauthorized')])
    self.notify_change()
    return self.render_reply(handler, title, [tag.p('Request sent')])


utils.import_site(globals())
